/*
 * Created on 31.01.2004
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package ro.qi.framework.sys.repository;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;

import oracle.jdbc.OracleTypes;
import oracle.jdbc.driver.OracleBlobOutputStream;
import oracle.jdbc.driver.OracleClobWriter;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import ro.qi.framework.sys.db.ConnectionManager;
import ro.qi.framework.sys.db.ConnectionPool;
import ro.qi.framework.sys.db.pool.JDCConnection;
import ro.qi.framework.sys.debug.Logger;
import ro.qi.framework.sys.entities.Entity;
import ro.qi.framework.sys.entities.components.ByteArray;
import ro.qi.framework.sys.entities.exception.RepositoryException;
import ro.qi.framework.sys.watchers.filesystem.FileInfo;
import ro.qi.framework.sys.watchers.filesystem.FileWatcher;
import ro.qi.framework.sys.watchers.filesystem.FileWatcherListener;

/**
 * @author Denverash
 *
 * To change the template for this generated type comment go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
public class Repository implements FileWatcherListener {

	// the configuration file
	public static final String CONFIGURATION_FILE = "config/configuration.xml";
	private static FileWatcher watcher = new FileWatcher();
	private DocumentBuilderFactory documentBuilderFactory = null;
	
	// collection of the entities defined in the configuration file
	private Hashtable entities = new Hashtable();

	// collection of the datasources defined in the configuration file
	private Hashtable datasources = new Hashtable();

	// collection of the connection pools in case of multiple jdbc connections
	private Hashtable connectionPools = new Hashtable();

	// the name of the default datasource
	private String defaultDatasourceName = null;

	// the instance of this class
	private static Repository instance = null;

	// debug mode or not? should be configurable externally in the future	
	private static final boolean _debug = false;
	private static final boolean _debug2 = false;

	public static boolean initialized = false;

	public Repository() {
		try {
			documentBuilderFactory = DocumentBuilderFactory.newInstance();
		} catch (FactoryConfigurationError e) {
			e.printStackTrace();
		}

		// watcher.start();
		scanConfigurationFiles();
	}

	private String getEntityName(Object obj) {
		return obj.getClass().getName();
	}

	private String getEntityFullName(Object obj) {
		String entityName = obj.getClass().getName();
		return entityName.substring(entityName.lastIndexOf(".") + 1);
	}

	private DatasourceConfiguration[] _getDatasources() {
		DatasourceConfiguration[] response = new DatasourceConfiguration[datasources.size()];
		Enumeration enum1 = datasources.keys();
		int i = 0;
		while( enum1.hasMoreElements() ) {
			String name = (String)enum1.nextElement();
			response[i++] = (DatasourceConfiguration)datasources.get(name);
		}
		return response;
	}

	public static DatasourceConfiguration[] getDatasources() {
		return getInstance()._getDatasources();
	}

	public static Connection getDatasourceConnection(String datasource) throws Exception {
		return getInstance().getConnection(datasource);
	}

	private String getDatasourceTemplate(String datasource) {
		DatasourceConfiguration datasourceConfiguration = (DatasourceConfiguration)datasources.get(datasource);
		if( datasourceConfiguration == null )
			datasourceConfiguration = getDefaultDatasource();
		if( datasourceConfiguration != null && datasourceConfiguration.getTemplate() != null){
			return datasourceConfiguration.getTemplate();
		}
		return null;
	}

	private DatasourceConfiguration getDefaultDatasource() {
		if( defaultDatasourceName != null )
			return (DatasourceConfiguration)datasources.get(defaultDatasourceName);
		Enumeration enum1 = datasources.keys();
		while( enum1.hasMoreElements() ) {
			String datasourceName = (String)enum1.nextElement();
			DatasourceConfiguration datasourceConfiguration = (DatasourceConfiguration)datasources.get(datasourceName);
			if( datasourceConfiguration != null && datasourceConfiguration.isDefault() ) {
				defaultDatasourceName = datasourceName;
				return datasourceConfiguration;
			}
		}
		return null;
	}

	/** Returns a datasources based on it's name from the entity configuration
	 * @param datasource
	 * @return
	 * @throws Exception
	 */
	private Connection getConnection(String datasource) throws Exception {
		DatasourceConfiguration datasourceConfiguration = (DatasourceConfiguration)datasources.get(datasource);

		if( datasourceConfiguration == null )
			datasourceConfiguration = getDefaultDatasource();
		if( datasourceConfiguration == null || datasourceConfiguration.getName() == null || (!datasourceConfiguration.getType().equals("jndi") && !datasourceConfiguration.getType().equals("jdbc")) ) {
			// if the datasource is unknown, shold throw an exception
			if( datasourceConfiguration != null )
				throw new RepositoryException("Unknown datasource type [" + datasourceConfiguration.getType() + "] in the datasource: " + datasource.toString());
			throw new RepositoryException("Datasource not defined");
		}
		if( datasourceConfiguration.getType().equals("jndi") ) {
			JndiDatasourceConfiguration jndiDatasourceConfiguration = (JndiDatasourceConfiguration)datasourceConfiguration;
			return new ConnectionManager(jndiDatasourceConfiguration.getJndiName()).getConnection();
		}

		if( datasourceConfiguration.getType().equals("jdbc") ) {
			JdbcDatasourceConfiguration jdbcDatasourceConfiguration = (JdbcDatasourceConfiguration)datasourceConfiguration;
			ConnectionPool connectionPool = (ConnectionPool)connectionPools.get(datasource);
			if( connectionPool == null ) {
				connectionPool = new ConnectionPool(jdbcDatasourceConfiguration.getUrl(), jdbcDatasourceConfiguration.getUsername(), jdbcDatasourceConfiguration.getPassword(), jdbcDatasourceConfiguration.getDriver());
				connectionPools.put(datasource, connectionPool);
			}
			Connection connection = connectionPool.getConnection();
			return connection;
		}

		throw new RepositoryException("Very strange... what are you doing here, at the end of the Repository.getConnection(String) method ??? I think you have a wrong type of datasource and I accept only jndi or jdbc types for now, if you want me to support your type of datasource, send me an e-mail, ok?");
	
	}

	private void _stop() {
		/*
		if( watcher != null )
			watcher.stop();
		*/
		if( connectionPools != null ) {
			ConnectionPool[] conns = (ConnectionPool[])connectionPools.values().toArray(new ConnectionPool[0]);
			for( int idx = 0 ; idx < conns.length ; idx++ ) {
				conns[idx].stop();
			}
		}
		/*
		entities.clear();
		datasources.clear();
		connectionPools.clear();

		watcher = null;
		documentBuilderFactory = null;
		entities = null;
		datasources = null;
		connectionPools = null;
		*/
	}

	public static void stop() {
		getInstance()._stop();
		// instance = null;
	}

	public static void returnDatasourceConnection(String datasource, Connection con) {
		getInstance().returnConnection(datasource, con);
	}
	
	private void returnConnection(String datasource, Connection con) {
		if (connectionPools == null)
			return;
		ConnectionPool connectionPool = (ConnectionPool)connectionPools.get(datasource);
		if (connectionPool == null) {
			try {
				con.close();
			} catch (SQLException e) {
				Logger.error(this, "Error returning the connection after using the datasource: " + datasource + " : " + e.getMessage());
			}
		} else {
			connectionPool.returnConnection((JDCConnection)con);
		}
	}

	private void executeAction(Entity entity, EntityConfiguration entityConfiguration, ActionConfiguration actionConfiguration) {
		// TODO: throw exceptions if the execution of the action failed
		Date startDate = null;
		if (_debug2) startDate = new Date();
		if (_debug2) Logger.debug(this, "start executing " + actionConfiguration.getContent());
		Connection con = null;
		CallableStatement cs = null;
		PreparedStatement ps = null;
		StringBuffer queryParameters = new StringBuffer();
		if( actionConfiguration.getType().equals("procedure") ) {
			// execute a procedure call
			try {
				Vector parsedAction = actionConfiguration.parseAction();
				StringBuffer query = new StringBuffer();
				for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
					ActionElement actionElement = (ActionElement)parsedAction.get(idx);
					switch( actionElement.getType() ) {
						case ActionElement.TYPE_NONE:
							query.append(actionElement.getComponentName());
						break;
						default:
							query.append("?");
						break;
					}
				}
				con = getConnection(entityConfiguration.getDatasource());
				cs = con.prepareCall(query.toString());
				if( _debug )
					Logger.debug(this, query.toString());
				int parameterIndex = 1;
				for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
					ActionElement actionElement = (ActionElement)parsedAction.get(idx);
					if( actionElement.getType() != ActionElement.TYPE_NONE ) {
						if( _debug ) {
							Logger.debug(this, parameterIndex + ": bounding element " + actionElement.getComponentName() + " : input = " + actionElement.isInput() + ": value = " + entity.getValue(actionElement.getComponentName()));
						}
						queryParameters.append(parameterIndex + ": bounding element " + actionElement.getComponentName() + " : input = " + actionElement.isInput() + ": value = " + entity.getValue(actionElement.getComponentName()) + "\n");
						if (actionElement.isInput()) {
							switch( actionElement.getType() ) {
								case ActionElement.TYPE_FLOAT:
									if( entity.getValue(actionElement.getComponentName()) != null )
										cs.setFloat(parameterIndex, ((Float)entity.getValue(actionElement.getComponentName())).floatValue());
									else
										cs.setNull(parameterIndex, Types.FLOAT);
//										Logger.error(this, actionElement.getComponentName() + " has NULL value");
								break;
								case ActionElement.TYPE_NUMERIC:
									if( entity.getValue(actionElement.getComponentName()) != null )
										cs.setInt(parameterIndex, ((Integer)entity.getValue(actionElement.getComponentName())).intValue());
									else
										cs.setNull(parameterIndex, Types.NUMERIC);
//										Logger.error(this, actionElement.getComponentName() + " has NULL value");
								break;
								case ActionElement.TYPE_BOOLEAN:
									// this is not working on oracle as the oracle jdbc driver can't map boolean to their internal data types (oracle doesn't have a boolean datatype)
									if (entity.getValue(actionElement.getComponentName()) != null) {
										cs.setBoolean(parameterIndex, ((Boolean)entity.getValue(actionElement.getComponentName())).booleanValue());
//										Logger.debug(this, actionElement.getComponentName() + " has boolean value: " + ((Boolean)entity.getValue(actionElement.getComponentName())).booleanValue());
									} else {
										cs.setNull(parameterIndex, Types.BOOLEAN);
//										Logger.error(this, actionElement.getComponentName() + " has NULL value");
									}
								break;
								case ActionElement.TYPE_VARCHAR:
								case ActionElement.TYPE_VARCHAR2:
								case ActionElement.TYPE_NVARCHAR:
									cs.setString(parameterIndex, (String)entity.getValue(actionElement.getComponentName()));
								break;
								case ActionElement.TYPE_DATE:
									Date date = (Date)entity.getValue(actionElement.getComponentName());
									if( date != null )
										cs.setTimestamp(parameterIndex, new Timestamp( (date).getTime() ));
									else
										cs.setTimestamp(parameterIndex, null);
								break;
								case ActionElement.TYPE_ORACLE_CLOB:
									cs.registerOutParameter(parameterIndex,OracleTypes.CLOB);
								break;
								case ActionElement.TYPE_ORACLE_BLOB:
									cs.registerOutParameter(parameterIndex,OracleTypes.BLOB);
								break;
							}
						} else {
							switch( actionElement.getType() ) {
								case ActionElement.TYPE_NUMERIC:
									cs.registerOutParameter(parameterIndex,Types.NUMERIC);
								break;
								case ActionElement.TYPE_FLOAT:
									cs.registerOutParameter(parameterIndex,Types.FLOAT);
								break;
								case ActionElement.TYPE_BOOLEAN:
									cs.registerOutParameter(parameterIndex,Types.BOOLEAN);
								break;
								case ActionElement.TYPE_VARCHAR:
								case ActionElement.TYPE_VARCHAR2:
								case ActionElement.TYPE_NVARCHAR:
									cs.registerOutParameter(parameterIndex,Types.VARCHAR);
								break;
								case ActionElement.TYPE_DATE:
									cs.registerOutParameter(parameterIndex,Types.TIMESTAMP);
								break;
								case ActionElement.TYPE_ORACLE_CURSOR:
									cs.registerOutParameter(parameterIndex,OracleTypes.CURSOR);
								break;
								case ActionElement.TYPE_ORACLE_CLOB:
									cs.registerOutParameter(parameterIndex,OracleTypes.CLOB);
								break;
								case ActionElement.TYPE_ORACLE_BLOB:
									cs.registerOutParameter(parameterIndex,OracleTypes.BLOB);
								break;
							}
						}
						parameterIndex++;
					}
				}
				cs.getConnection().setAutoCommit(false);
				// execute the procedure
				cs.execute();
				// read the output parameters, if any
				parameterIndex = 1;
				for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
					ActionElement actionElement = (ActionElement)parsedAction.get(idx);
					if( actionElement.getType() != ActionElement.TYPE_NONE ) {
						if( actionElement.isOutput() ) {
							switch( actionElement.getType() ) {
								case ActionElement.TYPE_NUMERIC:
									entity.setValue(actionElement.getComponentName(), new Integer(cs.getInt(parameterIndex)));
									// TODO; check to see if is working well
									if (cs.wasNull()) {
										entity.setValue(actionElement.getComponentName(), null);
									}
								break;
								case ActionElement.TYPE_FLOAT:
									entity.setValue(actionElement.getComponentName(), new Float(cs.getFloat(parameterIndex)));
									if (cs.wasNull()) {
										entity.setValue(actionElement.getComponentName(), null);
									}
								break;
								case ActionElement.TYPE_VARCHAR:
								case ActionElement.TYPE_VARCHAR2:
								case ActionElement.TYPE_NVARCHAR:
									entity.setValue(actionElement.getComponentName(), cs.getString(parameterIndex));
								break;
								case ActionElement.TYPE_DATE:
									entity.setValue(actionElement.getComponentName(), cs.getTimestamp(parameterIndex));
									if (cs.wasNull()) {
										entity.setValue(actionElement.getComponentName(), null);
									}
								break;
								case ActionElement.TYPE_ORACLE_CURSOR:
									entity.setValue(actionElement.getComponentName(), cs.getObject(parameterIndex));
								break;
								case ActionElement.TYPE_ORACLE_CLOB:
									oracle.sql.CLOB clob = (oracle.sql.CLOB)cs.getObject(parameterIndex);
									if( clob != null ) {
										long l=clob.length();
										entity.setValue(actionElement.getComponentName(), clob.getSubString(1,(int)l+1));
									}
								break;
								case ActionElement.TYPE_ORACLE_BLOB:
									oracle.sql.BLOB blob = (oracle.sql.BLOB)cs.getObject(parameterIndex);
									if( blob != null ) {
										entity.setValue(actionElement.getComponentName(), new ByteArray(blob.getBytes(1, (int) blob.length())));
									}
								break;
							}
						} else {
							switch( actionElement.getType() ) {
								case ActionElement.TYPE_ORACLE_CLOB:
									oracle.sql.CLOB clob = (oracle.sql.CLOB)cs.getObject(parameterIndex);
									if( clob != null ) {
										OracleClobWriter writer = (OracleClobWriter)clob.setCharacterStream(1L);
										writer.write((String)entity.getValue(actionElement.getComponentName()));
										writer.flush();
										writer.close();
									}
								break;
								case ActionElement.TYPE_ORACLE_BLOB:
									oracle.sql.BLOB blob = (oracle.sql.BLOB)cs.getObject(parameterIndex);
									if( blob != null ) {
										OracleBlobOutputStream blobOutputStream = (OracleBlobOutputStream)blob.setBinaryStream(1);
										ByteArray ba = (ByteArray)entity.getValue(actionElement.getComponentName());
										if( ba != null ) {
											ba.writeTo(blobOutputStream);
											blobOutputStream.flush();
										}
										blobOutputStream.close();
									}
								break;
							}
						}
						parameterIndex++;
					}
				}
				cs.getConnection().commit();
				cs.getConnection().setAutoCommit(true);
				cs.close();
				returnConnection(entityConfiguration.getDatasource(), con);
			} catch (Exception e) {
				e.printStackTrace();
				entity.setLastActionError(true);
				entity.setLastActionErrorDescription("Error executing action " + actionConfiguration.getContent() + ": " + e.getMessage() + " (datasource used: " + entityConfiguration.getDatasource() + ", default datasource: " + getDefaultDatasource().getName() + ")");
				try {
					try {
						if( con != null )
							con.rollback();
					} catch (Exception e1) {
						Logger.error(this, "Could not rollback after executing action " + actionConfiguration.getContent() + ": " + e.getMessage());
					}
					if( cs != null )
						cs.close();
					if( con != null )
						returnConnection(entityConfiguration.getDatasource(), con);
						//con.close();
				} catch (SQLException e1) {}
				Logger.error(this, "Error executing action " + actionConfiguration.getContent() + ": " + e.getMessage() + " (datasource used: " + entityConfiguration.getDatasource() + ", default datasource: " + getDefaultDatasource().getName() + ")");
				Logger.error(this, queryParameters.toString());
			}
		} else if( actionConfiguration.getType().startsWith("statement") ) {
			// execute a statement
			try {
				Vector parsedAction = actionConfiguration.parseAction();
				StringBuffer query = new StringBuffer();
				for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
					ActionElement actionElement = (ActionElement)parsedAction.get(idx);
					switch( actionElement.getType() ) {
						case ActionElement.TYPE_NONE:
							query.append(actionElement.getComponentName());
						break;
						default:
						{
							if( actionElement.isInput() ) {
								query.append("?");
							} else {
								ComponentConfiguration componentConfiguration = (ComponentConfiguration)entityConfiguration.getComponents().get(actionElement.getComponentName());
								if( componentConfiguration == null ) {
									Logger.error(this, "componentConfiguration == null for action element: " + actionElement.getComponentName());
								}
								switch( actionElement.getType() ) {
									case ActionElement.TYPE_COLLECTION:
									{
										if (actionElement.getExtra() != null ) {
											query.append(actionElement.getExtra());
										} else {
											NodeList configuration = componentConfiguration.getConfiguration();
											if( configuration != null && configuration.getLength() > 0 ) {
												int elementIdx = 0;
												for( int cidx = 0 ; cidx < configuration.getLength() ; cidx++ ) {
													Node configNode = configuration.item(cidx);
													if( configNode.getNodeType() == Node.ELEMENT_NODE ) {
														if( configNode.getNodeName().toLowerCase().equals("element") ) {
															String field = configNode.getAttributes().getNamedItem("field").getNodeValue();
															if( elementIdx++ > 0 )
																query.append(',');
															query.append(field);
														}
													}
												}
											} else {
												query.append("*");
											}
										}
									}
									break;
									default:
									if (componentConfiguration != null)
										query.append(componentConfiguration.getFieldName());
									break;
								}
							}
						}
						break;
					}
				}

				con = getConnection(entityConfiguration.getDatasource());
				ps = con.prepareStatement(query.toString());
				int parameterIndex = 1;
				for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
					ActionElement actionElement = (ActionElement)parsedAction.get(idx);
					if( actionElement.getType() != ActionElement.TYPE_NONE ) {
						if( actionElement.isInput() ) {
							switch( actionElement.getType() ) {
								case ActionElement.TYPE_FLOAT:
									if( entity.getValue(actionElement.getComponentName()) != null )
										ps.setFloat(parameterIndex, ((Float)entity.getValue(actionElement.getComponentName())).floatValue());
									else
										Logger.error(this, actionElement.getComponentName() + " has NULL value");
								break;
								case ActionElement.TYPE_NUMERIC:
									if( entity.getValue(actionElement.getComponentName()) != null )
										ps.setInt(parameterIndex, ((Integer)entity.getValue(actionElement.getComponentName())).intValue());
									else
										Logger.error(this, actionElement.getComponentName() + " has NULL value");
								break;
								case ActionElement.TYPE_CLOB:
								case ActionElement.TYPE_VARCHAR:
								case ActionElement.TYPE_VARCHAR2:
									ps.setString(parameterIndex, (String)entity.getValue(actionElement.getComponentName()));
								break;
								case ActionElement.TYPE_DATE:
									Date date = (Date)entity.getValue(actionElement.getComponentName());
									if( date != null )
										ps.setTimestamp(parameterIndex, new Timestamp( (date).getTime() ));
									else
										ps.setTimestamp(parameterIndex, null);
								break;
								case ActionElement.TYPE_BLOB:
								{
									ByteArray byteArray = ((ByteArray)entity.getValue(actionElement.getComponentName()));
									ps.setBinaryStream(parameterIndex, new ByteArrayInputStream(byteArray.bytes), byteArray.length());
								}
								break;
							}
							parameterIndex++;
						}
					}
				}
				// execute the statement
				ResultSet rs = null;
				ps.getConnection().setAutoCommit(false);
				String queryType = query.toString().toLowerCase();
				if( queryType.startsWith("update") ||
					queryType.startsWith("insert") ||
					queryType.startsWith("delete") ) {
					ps.executeUpdate();
				} else {
					rs = ps.executeQuery();
					if( actionConfiguration.getType().equals("statement") ) {
						if( rs.next() ) {
							// read the output parameters, if any
							parameterIndex = 1;
							for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
								ActionElement actionElement = (ActionElement)parsedAction.get(idx);
								if( actionElement.getType() != ActionElement.TYPE_NONE ) {
									if( actionElement.isOutput() ) {
										ComponentConfiguration componentConfiguration = (ComponentConfiguration)entityConfiguration.getComponents().get(actionElement.getComponentName());
										query.append(componentConfiguration.getFieldName());
										switch( actionElement.getType() ) {
											case ActionElement.TYPE_FLOAT:
												entity.setValue(actionElement.getComponentName(), new Float(rs.getFloat(componentConfiguration.getFieldName())));
											break;
											case ActionElement.TYPE_NUMERIC:
												entity.setValue(actionElement.getComponentName(), new Integer(rs.getInt(componentConfiguration.getFieldName())));
											break;
											case ActionElement.TYPE_CLOB:
											case ActionElement.TYPE_VARCHAR:
											case ActionElement.TYPE_VARCHAR2:
												entity.setValue(actionElement.getComponentName(), rs.getString(componentConfiguration.getFieldName()));
											break;
											case ActionElement.TYPE_DATE:
												entity.setValue(actionElement.getComponentName(), rs.getTimestamp(componentConfiguration.getFieldName()));
											break;
											case ActionElement.TYPE_BLOB:
												Blob blob = rs.getBlob(componentConfiguration.getFieldName());
												if( blob != null ) {
													entity.setValue(actionElement.getComponentName(), new ByteArray(blob.getBytes(1, (int) blob.length())));
												}
											break;
										}
									}
									parameterIndex++;
								}
							}
						}
					} else if( actionConfiguration.getType().equals("statement_collection") ) {
						for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
							ActionElement actionElement = (ActionElement)parsedAction.get(idx);
							if( actionElement.isOutput() ) {
								if( actionElement.getType() == ActionElement.TYPE_COLLECTION ) {
									entity.setValue(actionElement.getComponentName(), rs);
									break;
								}
							}
						}
					}
				}
				ps.getConnection().commit();
				ps.getConnection().setAutoCommit(true);
				if( rs != null )
					rs.close();
				ps.close();
				returnConnection(entityConfiguration.getDatasource(), con);
			} catch (Exception e) {
				e.printStackTrace();
				entity.setLastActionError(true);
				entity.setLastActionErrorDescription("error executing action " + actionConfiguration.getContent() + ": " + e.getMessage());
				try {
					if( ps != null )
						ps.close();
					if( con != null )
						returnConnection(entityConfiguration.getDatasource(), con);
				} catch (SQLException e1) {}
				Logger.error(this, "error executing action " + actionConfiguration.getContent() + ": " + e.getMessage());
				Vector parsedAction = actionConfiguration.parseAction();
				for( int idx = 0 ; idx < parsedAction.size() ; idx++ ) {
					ActionElement actionElement = (ActionElement)parsedAction.get(idx);
					Logger.debug(this, "Bounding element " + actionElement.getComponentName() + " : input = " + actionElement.isInput() + ": value = " + entity.getValue(actionElement.getComponentName()));
				}
			}
		}
		Date endDate = null;
		if (_debug2) endDate = new Date();
		if (_debug2) Logger.debug(this, (endDate.getTime() - startDate.getTime()) + "ms for " + actionConfiguration.getContent());
	}

	private void preExecuteAction(Entity entity, String selection) {
		entity.setLastActionError(false);
		entity.setLastActionErrorDescription(null);

		String entityName = getEntityName(entity);
		EntityConfiguration entityConfiguration = (EntityConfiguration)entities.get(entityName);
		if( entityConfiguration == null ) {
			entityName = getEntityFullName(entity);
			entityConfiguration = (EntityConfiguration)entities.get(entityName);
		}
		if( entityConfiguration != null ) {
			if( entityConfiguration.getDatasource() != null && entityConfiguration.getDatasource().length() > 0 ) {
				Vector actions = entityConfiguration.getActions(selection);
				for( int idx = 0 ; idx < actions.size() ; idx++ ) {
					ActionConfiguration actionConfiguration = (ActionConfiguration)actions.get(idx);
					String templateName = getDatasourceTemplate(entityConfiguration.getDatasource());
					if( actionConfiguration.matchTemplate(templateName) ) {
						if( _debug ) Logger.debug(this, "I will execute action " + actionConfiguration + " of entity " + entityName);
						executeAction(entity, entityConfiguration, actionConfiguration);
					} else {
						if( _debug ) Logger.debug(this, "I won't execute action " + actionConfiguration + " of entity " + entityName);
					}
				}
			} else {
				entity.setLastActionError(true);
				entity.setLastActionErrorDescription("the entity " + entityName + " is not bound to a datasource");
				Logger.error(this, "the entity " + entityName + " is not bound to a datasource");
			}
		} else {
			entity.setLastActionError(true);
			entity.setLastActionErrorDescription("could not find the entity " + entityName);
			Logger.error(this, "could not find the entity " + entityName);
		}
	}

	public void _select(Entity entity) {
		preExecuteAction(entity, "select");
	}

	public void _update(Entity entity) {
		preExecuteAction(entity, "update");
	}

	public void _add(Entity entity) {
		preExecuteAction(entity, "add");
	}

	public void _delete(Entity entity) {
		preExecuteAction(entity, "delete");
	}

	public void _action(Entity entity, String action) {
		preExecuteAction(entity, action);
	}

	public static void action(Entity entity, String action) {
		getInstance()._action(entity, action);
	}

	public static Repository getInstance() {
		if( instance == null ) {
			instance = new Repository();
			initialized = true;
		}
		return instance;
	}

	public void _getEntity(Entity entity) {
		String entityName = getEntityName(entity);
		EntityConfiguration entityConfiguration = (EntityConfiguration)entities.get(entityName);
		if( entityConfiguration == null ) {
			entityName = getEntityFullName(entity);
			entityConfiguration = (EntityConfiguration)entities.get(entityName);
		}
		if( entityConfiguration != null ) {
			Hashtable components = new Hashtable();
			Hashtable entityComponents = entityConfiguration.getComponents();
			Enumeration enum1 = entityComponents.keys();
			while( enum1.hasMoreElements() ) {
				String componentName = (String)enum1.nextElement();
				ComponentConfiguration componentConfiguration = (ComponentConfiguration)entityComponents.get(componentName);
				try {
					Object componentInstance = componentConfiguration.getComponentInstance();
					if( componentInstance != null ) {
						components.put(componentName, componentInstance);
					}
				} catch (NullPointerException e) {
					Logger.error(this, "NULL POINTER EXCEPTION: entityName.componentName = " + entityName + "." + componentName);
				}
			}
			entity.setComponents(components);
		}
	}

	public static void select(Entity entity) {
		getInstance()._select(entity);
	}

	public static void update(Entity entity) {
		getInstance()._update(entity);
	}

	public static void add(Entity entity) {
		getInstance()._add(entity);
	}

	public static void delete(Entity entity) {
		getInstance()._delete(entity);
	}

	public static void getEntity(Entity entity) {
		getInstance()._getEntity(entity);
	}

	public static void readConfiguration(String path, InputStream stream) throws Exception {
		getInstance().readConfigurationFile(path, stream);
	}

	private void scanConfigurationFiles() {
		scanConfiguration(CONFIGURATION_FILE);
	}

	private synchronized void scanConfiguration(String resourceName) {
		// Scans the system for configuration files
		try {
			//Logger.info(this, "scans the system for configuration files: " + resourceName);
			Enumeration enum1 = this.getClass().getClassLoader().getResources(resourceName);
			readConfigurationFiles(enum1, resourceName);
			enum1 = Thread.currentThread().getContextClassLoader().getResources(resourceName);
			readConfigurationFiles(enum1, resourceName);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Vector configFiles = new Vector();

	private synchronized void readConfigurationFiles(Enumeration enum1, String resourceName) throws Exception {
		while( enum1.hasMoreElements() ) {
			URL url = (URL)enum1.nextElement();
			//Logger.info(this, "checks for configs: " + url.getPath());
			String path = url.getPath();
			if( path.startsWith("file:") )
				path = path.substring("file:".length());
			// if( path.startsWith("/") ) path = path.substring(1);
			// check if the file is archived in a .jar
			if( path.indexOf('!') == -1 ) {
				// if we don't have an archive
				//Logger.info(this, "we don't have an archive");
				//Logger.info(this, "added to filewatcher: " + path);
				if (path != null) {
					if (!configFiles.contains(path)) {
						configFiles.add(path);
						watcher.addWatch(this, path);
						Logger.info(this, "reading configuration from file: " + path);
						readConfigurationFile(path, null);
					}
				}
			} else {
				try {
					//Logger.info(this, "we have an archive for entry: " + path);
					// if we have an archive
					String fullPath = path;
					path = path.substring(0, path.indexOf('!'));
					//Logger.info(this, "we found out that the jar name is: " + path);
					JarFile jarFile = new JarFile(new File(path));
					//Logger.info(this, "jarFile: " + jarFile);
					JarEntry jarEntry = jarFile.getJarEntry(resourceName);
					//Logger.info(this, "jarEntry: " + jarEntry);
					if( jarEntry != null ) {
						//Logger.info(this, "added to filewatcher: " + path);
						if (path != null) {
							if (!configFiles.contains(fullPath)) {
								configFiles.add(fullPath);
								watcher.addWatch(this, path);
								Logger.info(this, "reading configuration from archive: " + fullPath);
								readConfigurationFile(null, jarFile.getInputStream( jarEntry ));

//								BufferedReader bin = new BufferedReader(new InputStreamReader(jarFile.getInputStream( jarEntry )));
//								StringBuffer buf = new StringBuffer();
//								String line = "";
//								while ((line = bin.readLine()) != null)
//									buf.append(line + "\r\n");
//								Logger.info(this, "input stream: " + buf);
							}
						}
						jarFile.close();
					} else {
						Logger.info(this, "jar entry is NULL for entry: " + resourceName);
					}
					jarFile.close();
					jarFile = null;
				} catch (IOException e) {
					Logger.error("Could not read jar file: " + path);
				}
			}
		}
	}

	public synchronized void readConfigurationFile(String path, InputStream stream) throws Exception {
		Document doc = null;
		if( path != null ) {
			//Logger.debug(this, "reading from path " + path);
			doc = documentBuilderFactory.newDocumentBuilder().parse(new File(path));
		} else {
			//Logger.debug(this, "reading from stream");
			doc = documentBuilderFactory.newDocumentBuilder().parse(stream);
		}

		//Logger.info(this, "reading datasource files");
		// read the datasource files
		NodeList datasourceFileNodes = doc.getElementsByTagName("datasources-file");
		for( int idx = 0 ; idx < datasourceFileNodes.getLength() ; idx++ ) {
			Node datasourceFileNode = datasourceFileNodes.item(idx);
			scanConfiguration(datasourceFileNode.getFirstChild().getNodeValue());
		}

		//Logger.info(this, "reading entity files");
		// read the entity files
		NodeList entityFileNodes = doc.getElementsByTagName("entities-file");
		for( int idx = 0 ; idx < entityFileNodes.getLength() ; idx++ ) {
			Node entityFileNode = entityFileNodes.item(idx);
			scanConfiguration(entityFileNode.getFirstChild().getNodeValue());
		}

		//Logger.info(this, "reading datasources");
		// read the datasources
		NodeList datasourcesNodes = doc.getElementsByTagName("datasource");
		for( int idx = 0 ; idx < datasourcesNodes.getLength() ; idx++ ) {
			Node datasourceNode = datasourcesNodes.item(idx);
			NamedNodeMap datasourceAttributes = datasourceNode.getAttributes();
			if( datasourceAttributes != null && datasourceAttributes.getLength() > 0 ) {
				Node datasourceName = datasourceAttributes.getNamedItem("name");
				Node datasourceTemplate = datasourceAttributes.getNamedItem("template");
				Node datasourceType = datasourceAttributes.getNamedItem("type");
				Node datasourceDefault = datasourceAttributes.getNamedItem("default");
				if( datasourceName != null && datasourceType != null ) {
					String name = datasourceName.getNodeValue();
					String type = datasourceType.getNodeValue();
					boolean isDefault = ((datasourceDefault != null) && (datasourceDefault.getNodeValue() != null) && (datasourceDefault.getNodeValue().equals("true")));
					String template = null;
					if( datasourceTemplate != null )
						template = datasourceTemplate.getNodeValue();
					DatasourceConfiguration configuration = null;
					if( type.equals("jndi") || type.equals("jdbc") ) {
						if( type.equals("jndi") ) {
							configuration = new JndiDatasourceConfiguration();
							configuration.setName(name);
							configuration.setType(type);
							configuration.setTemplate(template);
							configuration.setDefault(isDefault);
							Node jndiName = datasourceAttributes.getNamedItem("jndi_name");
							if( jndiName != null ) {
								((JndiDatasourceConfiguration)configuration).setJndiName(jndiName.getNodeValue());
								datasources.put(name, configuration);
								Logger.debug(this, configuration.toString());
							}
						} else {
							configuration = new JdbcDatasourceConfiguration();
							configuration.setName(name);
							configuration.setType(type);
							configuration.setTemplate(template);
							configuration.setDefault(isDefault);
							Node driver = datasourceAttributes.getNamedItem("driver");
							Node username = datasourceAttributes.getNamedItem("username");
							Node password = datasourceAttributes.getNamedItem("password");
							Node url = datasourceAttributes.getNamedItem("url");
							if( driver != null && username != null && password != null && url != null ) {
								((JdbcDatasourceConfiguration)configuration).setDriver(driver.getNodeValue());
								((JdbcDatasourceConfiguration)configuration).setUsername(username.getNodeValue());
								((JdbcDatasourceConfiguration)configuration).setPassword(password.getNodeValue());
								((JdbcDatasourceConfiguration)configuration).setUrl(url.getNodeValue());
								datasources.put(name, configuration);
							}
						}
					}
				}
			}
		}

		//Logger.info(this, "reading entities");
		// read the entities
		NodeList entitiesNodes = doc.getElementsByTagName("entity");
		for( int idx = 0 ; idx < entitiesNodes.getLength() ; idx++ ) {
			Node entityNode = entitiesNodes.item(idx);
			if(entityNode.getNodeType() == Node.ELEMENT_NODE) {
				Element entity = (Element)entityNode;
				if( entity.getAttributes().getLength() > 0 ) {
					// if has attributes
					EntityConfiguration entityConfiguration = new EntityConfiguration();
					if( entity.getAttributes().getNamedItem("package") != null )
						entityConfiguration.setPackage(entity.getAttributes().getNamedItem("package").getNodeValue());
					if( entity.getAttributes().getNamedItem("name") != null )
						entityConfiguration.setName(entity.getAttributes().getNamedItem("name").getNodeValue());
					if (_debug) Logger.info(this, "reading entity: " + entityConfiguration.getName());
					if( entity.getAttributes().getNamedItem("version") != null )
						entityConfiguration.setVersion(entity.getAttributes().getNamedItem("version").getNodeValue());
					if( entity.getAttributes().getNamedItem("datasource") != null )
						entityConfiguration.setDatasource(entity.getAttributes().getNamedItem("datasource").getNodeValue());

					// read the actions of this entity
					NodeList importsNodes = entity.getElementsByTagName("import");
					for( int i = 0 ; i < importsNodes.getLength() ; i++ ) {
						Node importNode = importsNodes.item(i);
						if( importNode.getAttributes().getLength() > 0 ) {
							if( importNode.getAttributes().getNamedItem("entity") != null ) {
								String importedEntity = importNode.getAttributes().getNamedItem("entity").getNodeValue();
								String importType = importNode.getAttributes().getNamedItem("type").getNodeValue();
								if( importType == null ) {
									importType = "all";
								}

								Element baseEntity = findEntityElement(doc, importedEntity);
								if( baseEntity != null ) {
									// this entity is defined in the same .xml
									if( importType.equals("all") ) {
										readEntityComponents(baseEntity, entityConfiguration);
										readEntityActions(baseEntity, entityConfiguration);
									} else if( importType.equals("components") ) {
										readEntityComponents(baseEntity, entityConfiguration);
									} else if( importType.equals("actions") ) {
										readEntityActions(baseEntity, entityConfiguration);
									}
								} else {
									// maybe is already known
									if (entities.containsKey(importedEntity)) {
										EntityConfiguration importedEntityConfiguration = (EntityConfiguration)entities.get(importedEntity);
										if( importedEntityConfiguration == null ) {
											importedEntity = getEntityFullName(importedEntity);
											importedEntityConfiguration = (EntityConfiguration)entities.get(importedEntity);
										}
										if( importType.equals("all") ) {
											entityConfiguration.setComponents(importedEntityConfiguration.getComponents());
											entityConfiguration.setActions(importedEntityConfiguration.getActions());
										} else if( importType.equals("components") ) {
											entityConfiguration.setComponents(importedEntityConfiguration.getComponents());
										} else if( importType.equals("actions") ) {
											entityConfiguration.setActions(importedEntityConfiguration.getActions());
										}
									} else {
										Logger.error(this, "imported entity " + importedEntity + " not found");
									}
								}
							}
						}
					}

					readEntityComponents(entity, entityConfiguration);
					readEntityActions(entity, entityConfiguration);
					if (_debug) Logger.debug("entity configuration: " + entityConfiguration.toString());
					entities.put(entityConfiguration.getFullName(), entityConfiguration);
				}
			}
		}
	}

	private Element findEntityElement(Document doc, String entityName) {
		NodeList tmpEntitiesNodes = doc.getElementsByTagName("entity");
		for( int tmpidx = 0 ; tmpidx < tmpEntitiesNodes.getLength() ; tmpidx++ ) {
			Node tmpEntityNode = tmpEntitiesNodes.item(tmpidx);
			if(tmpEntityNode.getNodeType() == Node.ELEMENT_NODE) {
				Element entity = (Element)tmpEntityNode;
				if( entity.getAttributes().getLength() > 0 ) {
					// if has attributes
					if( entity.getAttributes().getNamedItem("name") != null )
						if( entityName.equals(entity.getAttributes().getNamedItem("name").getNodeValue()) )
							return entity;
				}
			}
		}
		return null;
	}

	private void readEntityComponents(Element entity, EntityConfiguration entityConfiguration) {
		// read the components of this entity
		NodeList componentsNodes = entity.getElementsByTagName("component");
		for( int i = 0 ; i < componentsNodes.getLength() ; i++ ) {
			Node component = componentsNodes.item(i);
			if( component.getParentNode() == entity && component.getAttributes().getLength() > 0 ) {
				ComponentConfiguration componentConfiguration = new ComponentConfiguration();
				if( component.getAttributes().getNamedItem("name") != null )
					componentConfiguration.setName(component.getAttributes().getNamedItem("name").getNodeValue());
				if( component.getAttributes().getNamedItem("class") != null )
					componentConfiguration.setClassName(component.getAttributes().getNamedItem("class").getNodeValue());
				if( component.getAttributes().getNamedItem("default_value") != null )
					componentConfiguration.setDefaultValue(component.getAttributes().getNamedItem("default_value").getNodeValue());
				if( component.getAttributes().getNamedItem("field") != null )
					componentConfiguration.setFieldName(component.getAttributes().getNamedItem("field").getNodeValue());
				if( component.hasChildNodes() ) {
					componentConfiguration.setConfiguration(component.getChildNodes());
				}
				entityConfiguration.addComponentConfiguration(componentConfiguration);
			}
		}
	}

	private void readEntityActions(Element entity, EntityConfiguration entityConfiguration) {
		// read the actions of this entity
		NodeList actionsNodes = entity.getElementsByTagName("action");
		for( int i = 0 ; i < actionsNodes.getLength() ; i++ ) {
			Node action = actionsNodes.item(i);
			if( action.getAttributes().getLength() > 0 ) {
				ActionConfiguration actionConfiguration = new ActionConfiguration();
				if( action.getAttributes().getNamedItem("name") != null )
					actionConfiguration.setName(action.getAttributes().getNamedItem("name").getNodeValue());
				if( action.getAttributes().getNamedItem("type") != null )
					actionConfiguration.setType(action.getAttributes().getNamedItem("type").getNodeValue());
				if( action.getAttributes().getNamedItem("template") != null )
					actionConfiguration.setTemplate(action.getAttributes().getNamedItem("template").getNodeValue());
				if( action.hasChildNodes() ) {
					actionConfiguration.setContent(action.getFirstChild().getNodeValue());
				} else {
					actionConfiguration.setContent("");
				}
				actionConfiguration.setContent(URLDecoder.decode(actionConfiguration.getContent()));
				entityConfiguration.addAction(actionConfiguration);
			}
		}
	}

	/**
	 * @param string
	 */
	public static void setConfigurationPath(String string) throws Exception {
		//configurationPath = string;
		getInstance().readConfigurationFile(string, null);
	}

	/**
	 * @see ro.qi.framework.watchers.filesystem.FileWatcherListener#fileAdded(ro.qi.framework.watchers.filesystem.FileInfo)
	 */
	public void fileAdded(FileInfo fileInfo) {
		Logger.info(this, "file added: " + fileInfo.getFilename());
	}

	/**
	 * @see ro.qi.framework.watchers.filesystem.FileWatcherListener#fileDeleted(ro.qi.framework.watchers.filesystem.FileInfo)
	 */
	public void fileDeleted(FileInfo fileInfo) {
		Logger.info(this, "file deleted: " + fileInfo.getFilename());
	}

	/**
	 * Should update in memory the xml configuration files
	 * (what if we are in the middle of a query execution? ==> now this method does nothing)
	 * @see ro.qi.framework.watchers.filesystem.FileWatcherListener#fileUpdated(ro.qi.framework.watchers.filesystem.FileInfo)
	 */
	public void fileUpdated(FileInfo fileInfo) {
		/*
		Logger.info(instance, "reading configuration from: " + fileInfo.getFilename());
		if( fileInfo.getFilename().indexOf('!') == -1 ) {
			readConfiguration(fileInfo.getFilename(), null);
		} else {
			String path = fileInfo.getFilename();
			path = path.substring(0, path.indexOf('!'));
			try {
				JarFile jarFile = new JarFile(new File(path));
				JarEntry jarEntry = jarFile.getJarEntry(CONFIGURATION_FILE);
				if( jarEntry != null ) {
					Logger.info(instance, "reading configuration from: " + path);
					instance._readConfiguration(null, jarFile.getInputStream( jarEntry ));
				}
				jarFile.close();
				jarFile = null;
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
		*/
	}

}
